/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  SRVIOStream.m - iPhoneSRV console for Surveyor SRV-1 and SVS 
 *
 *  Created by Nick Kitchener.  Copyright (C) 2009  Surveyor Corporation 
 *            and Nick Kitchener.  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details (www.gnu.org/licenses)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

//	Handles Cocoa I/O stream interations against SRVStreamableObject delegates. 

#import "SRVIOStream.h"
#import "SRVInterthreadMessage.h"
#import "SRVInterthreadMessageConnect.h"
#import "SRVInterthreadMessageStreamable.h"
#import "SRVInterthreadMessageCheckIn.h"

@implementation SRVIOStream
@synthesize parentThread, parent, thread, runLoop, currentObject, inputStreamOpened, outputStreamOpened;

static void inputStreamCallBack (CFReadStreamRef stream, CFStreamEventType event, void *myPtr);
static void outputStreamCallBack (CFWriteStreamRef stream, CFStreamEventType event, void *myPtr);

// instance (pre-threading) initialisation
-(id)init {
	threadTerminate = NO;
	//inputStream = nil;
	//outputStream = nil;
	currentObject = nil;
	inputStreamOpened = NO;
	outputStreamOpened = NO;
	[super init];
	return self;
}

-(void)finalize {
	NSLog(@"SRVIOStream instance finalize called");
	[super finalize];
}

//===================================================================
// Child thread start
-(void)startChildThread:(NSObject*)dummy {
	NSLog(@"SRVIOStream: Child thread entering main loop.");	
	NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];
	
	// Create child run loop
	thread = [NSThread currentThread];
	runLoop = [NSRunLoop currentRunLoop];
	
	// do checkin to tell parent that the values are set.
	SRVInterthreadMessageCheckIn *msg = [[SRVInterthreadMessageCheckIn alloc]init];
	[self sendMessage:msg];
	
	cfRunLoop = [runLoop getCFRunLoop];
	
	// run
	do {
		CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES);
	} while(!threadTerminate);
	
	NSLog(@"SRVIOStream: Child thread terminating.");
	
	[pool release];
}

-(void)sendMessage:(SRVInterthreadMessage*)message {	
	[parent performSelector:@selector(handleChildMessage:) onThread:parentThread withObject:message waitUntilDone:NO];
}

// port handler for this thread
// this handles all the messages from the master.
-(void)handleParentMessage:(SRVInterthreadMessage*)msg {
	
	switch( [msg type] ) {
		case CHECKIN:
			NSLog(@"SRVIOStream received own checkin message!"); // immortal words - this should never happen.
			break;
		case CONNECT:
			NSLog(@"SRVIOStream received Connect message..");
			SRVInterthreadMessageConnect *connectMsg = (SRVInterthreadMessageConnect*) msg;
			[self openStreams:[connectMsg address] port:[connectMsg portNumber]];
			//[connectMsg release];
			break;
		case DISCONNECT:
			NSLog(@"SRVIOStream received Disconnect message..");
			[self disconnectStreams];
			[self sendMessage:msg];
			break;	
		case STREAMABLE: {
			// Streamable Object.. 
			SRVInterthreadMessageStreamable *tmp = (SRVInterthreadMessageStreamable*) msg;
			currentObject = [tmp object];
			[tmp release];
//			NSLog(@"SRVIOStream received Streamable message.. %@", currentObject);
			//[currentObject haveSpace:outputStream];
			[currentObject haveSpace:cfOutputStream];
			}
			break;
		default:
			NSLog(@"Received unknown interthread message type = @", msg);
			break;
	}
}

//-------------------------------------------------------------------
// methods called from the message port handler


-(void)objectStreamingComplete
{
//	NSLog(@"SRVIOStream streaming object complete");
	SRVInterthreadMessageStreamable *tmp = [[SRVInterthreadMessageStreamable alloc] init];
	[tmp setObject:currentObject];
	[self sendMessage:tmp];
}

//-------------------------------------------------------------------
// byte level stream I/O handling interfacing the SRV

-(void)openStreams:(NSString*)targetURL port:(NSInteger)portNumber
{
	NSLog(@"SRVIOStream - attempting to open streams to target %@, port =%i", targetURL, portNumber);
	
	CFStreamCreatePairWithSocketToHost( kCFAllocatorDefault,
									   (CFStringRef)targetURL,
									   portNumber,
									   &cfInputStream,
									   &cfOutputStream);
	
	CFStreamClientContext inputContext = {0, self, NULL, NULL, NULL};
	
	if( CFReadStreamSetClient(cfInputStream, kCFStreamEventOpenCompleted|kCFStreamEventHasBytesAvailable|kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered, &inputStreamCallBack, &inputContext)) {
		CFReadStreamScheduleWithRunLoop(cfInputStream, cfRunLoop, kCFRunLoopCommonModes);
	}
	
	CFStreamClientContext outputContext = {0, self, NULL, NULL, NULL};

	if( CFWriteStreamSetClient(cfOutputStream, kCFStreamEventOpenCompleted|kCFStreamEventCanAcceptBytes|kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered, &outputStreamCallBack, &outputContext)) {
		CFWriteStreamScheduleWithRunLoop(cfOutputStream, cfRunLoop, kCFRunLoopCommonModes);
	}
	
	if( !CFReadStreamOpen(cfInputStream) ) {
		CFStreamError streamError = CFReadStreamGetError(cfInputStream);
		if( streamError.error != 0 ) {
			if( streamError.domain == kCFStreamErrorDomainPOSIX ) {
				NSLog(@" error opening read stream: %s", strerror(streamError.error));
			} else if( streamError.domain == kCFStreamErrorDomainMacOSStatus ) {
				NSLog(@" error opening read stream: %i", streamError.error);
			}
		}
	}
	
	if( !CFWriteStreamOpen(cfOutputStream) ) {
		CFStreamError streamError = CFWriteStreamGetError(cfOutputStream);
		if( streamError.error != 0 ) {
			if( streamError.domain == kCFStreamErrorDomainPOSIX ) {
				NSLog(@" error opening write stream: %s", strerror(streamError.error));
			} else if( streamError.domain == kCFStreamErrorDomainMacOSStatus ) {
				NSLog(@" error opening write stream: %i", streamError.error);
			}
		}
	}
	
	NSLog(@"gets here");
	
	/*
	NSHost *matchPortHost = [NSHost hostWithAddress:targetURL];
	if( matchPortHost == nil )
		NSLog(@"SRVIOStream: ERROR: Specified IP address is invalid");

	[NSStream getStreamsToHost:matchPortHost
						  port:portNumber
				   inputStream:&inputStream
				  outputStream:&outputStream];
	
	if( (inputStream==nil) || (outputStream==nil) ) {
		NSLog(@"SRVIOStream interface %@:%i failed to connect.", targetURL, portNumber);
	} else {
		[inputStream retain];
		[outputStream retain];
		[inputStream setDelegate:self];
		[outputStream setDelegate:self];
		[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
							   forMode:NSDefaultRunLoopMode];
		[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
								forMode:NSDefaultRunLoopMode];
		[inputStream open];
		[outputStream open];
	}*/
}

-(void)disconnectStreams {
	
	CFReadStreamUnscheduleFromRunLoop(cfInputStream, cfRunLoop, kCFRunLoopCommonModes);	
	CFReadStreamClose(cfInputStream);
	CFRelease(cfInputStream);
	
	CFWriteStreamUnscheduleFromRunLoop(cfOutputStream, cfRunLoop, kCFRunLoopCommonModes);
	CFWriteStreamClose(cfOutputStream);
	CFRelease(cfOutputStream);
/*	
	[outputStream close];
	[inputStream close];
	[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
							forMode:NSDefaultRunLoopMode];
	[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
						   forMode:NSDefaultRunLoopMode];
	[outputStream release];
	[inputStream release];*/
	NSLog(@"SRVIOStream interface disconnected.");
}

static void inputStreamCallBack(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
	SRVIOStream *s = (SRVIOStream*)myPtr;
	switch(event) {
		case kCFStreamEventOpenCompleted: 
			[s setInputStreamOpened:YES];
			if( [s inputStreamOpened] && [s outputStreamOpened] ) {
				NSLog(@"SRVIOStream interface connected.");
				SRVInterthreadMessageConnect *returnMsg = [[SRVInterthreadMessageConnect alloc] init];
				[s sendMessage:returnMsg];
			}
			break;
			
		case kCFStreamEventHasBytesAvailable: {
			SRVStreamableObject	*currentObject = [s currentObject]; 
			if( currentObject )
				[currentObject haveBytes:stream];
			if( [currentObject complete] )
				[s objectStreamingComplete];
			}
			break;
		case kCFStreamEventErrorOccurred:
			NSLog(@"input stream callback called - error occured");
			break;
		case kCFStreamEventEndEncountered:
			NSLog(@"input stream callback called - end occured");
			break;
		default:
			break;
	}
}

static void outputStreamCallBack(CFWriteStreamRef stream, CFStreamEventType event, void *myPtr) {
	SRVIOStream *s = (SRVIOStream*)myPtr;
	switch(event) {
		case kCFStreamEventOpenCompleted:
			[s setOutputStreamOpened:YES];
			if( [s inputStreamOpened] && [s outputStreamOpened] ) {
				NSLog(@"SRVIOStream interface connected.");
				SRVInterthreadMessageConnect *returnMsg = [[SRVInterthreadMessageConnect alloc] init];
				[s sendMessage:returnMsg];
			}
			break;

		case kCFStreamEventCanAcceptBytes: {
			SRVIOStream *s = (SRVIOStream*)myPtr;
			SRVStreamableObject	*currentObject = [s currentObject]; 
			if( currentObject ) 
				[currentObject haveSpace:stream];
			if( [currentObject complete] ) {
				[s objectStreamingComplete];
			}
			}
			break;
		case kCFStreamEventErrorOccurred:
			NSLog(@"write stream callback called - error occured");
			break;
		case kCFStreamEventEndEncountered:
			NSLog(@"write stream callback called - end occured");

			break;
		default:
			break;
	}
}


/*
// stream handler that handles all the stream events from the RunLoop
// it's important to provide the bytes to the operation due to the varying way the operations work.
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
	switch(streamEvent) {
		case NSStreamEventNone:
			break;
		case NSStreamEventOpenCompleted:
			if( theStream == inputStream )
				inputStreamOpened = YES;
			if( theStream == outputStream )
				outputStreamOpened = YES;
			// need those to prevent race conditions with incoming data!
			if( inputStreamOpened && outputStreamOpened ) {
				NSLog(@"SRVIOStream interface connected.");
				SRVInterthreadMessageConnect *returnMsg = [[SRVInterthreadMessageConnect alloc] init];
				[self sendMessage:returnMsg];
			}
			break;
		case NSStreamEventHasBytesAvailable: // bytes received from SRV
			if( currentObject )
				[currentObject haveBytes:(NSInputStream*)theStream];
			if( [currentObject complete] )
				[self objectStreamingComplete];
			break;
        case NSStreamEventHasSpaceAvailable: // space to send to SRV
			if( currentObject ) 
				[currentObject haveSpace:(NSOutputStream*)theStream];
			if( [currentObject complete] )
				[self objectStreamingComplete];
			break;
		case NSStreamEventEndEncountered: // terminated stream
			if( currentObject )
				[currentObject streamError];
			break;
		case NSStreamEventErrorOccurred: // oops
			if( currentObject )
				[currentObject streamError];
			break;		
	}
}
*/
@end
